﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace Mindfor.Web.Data
{
	/// <summary>
	/// Class for page data object.
	/// </summary>
	[Serializable]
	public class Page : IdEntity
	{
		/// <summary>
		/// Gets or sets pages action execution information.
		/// </summary>
		[DisplayName("Action")]
		public virtual ActionPath ActionPath { get; set; }

		/// <summary>
		/// Gets or sets parent page. Current page is root if parent is null.
		/// </summary>
		public virtual Page Parent { get; set; }

		/// <summary>
		/// Gets or sets name used in page url.
		/// </summary>
		[DisplayName("Путь")]
		[StringLength(255, ErrorMessage="Путь должен быть короче 255 символов")]
		[RegularExpression(@"^[a-zA-Z0-9\-_]+$", ErrorMessage="Путь может содержать только символы латинского алфавита, цифры, символы тире и подчеркивания")]
		public virtual string Name { get; set; }
		
		/// <summary>
		/// Gets or sets sort order.
		/// </summary>
		public virtual int Sort { get; set; }

		/// <summary>
		/// Gets or sets create date.
		/// </summary>
		public virtual DateTime CreateDate { get; set; }

		/// <summary>
		/// Gets or sets whether to show this element in menu.
		/// </summary>
		[DisplayName("Показывать в меню")]
		public virtual bool ShowInMenu { get; set; }

		/// <summary>
		/// Gets or sets whether to allow anonymous user to see page.
		/// </summary>
		public virtual bool AllowAnonymous { get; set; }

		/// <summary>
		/// Gets or sets whether to allow authorized user to see page.
		/// </summary>
		public virtual bool AllowAuthorized { get; set; }

		/// <summary>
		/// Gets or sets whether to open page in new window.
		/// </summary>
		[DisplayName("Открывать в новом окне")]
		public virtual bool OpenInNewWindow { get; set; }

		/// <summary>
		/// Gets page image.
		/// </summary>
		[DisplayName("Иконка меню")]
		public virtual byte[] Image { get; set; }

		/// <summary>
		/// Gets page tags.
		/// </summary>
		[DisplayName("Теги")]
		public virtual IList<Tag> Tags { get; set; }

		/// <summary>
		/// Gets page texts.
		/// </summary>
		public virtual IList<PageText> Texts { get; set; }

		/// <summary>
		/// Gets child pages.
		/// </summary>
		public virtual IList<Page> ChildPages { get; set; }

		/// <summary>
		/// Gets languages for which PageText exists.
		/// </summary>
		public virtual IEnumerable<Language> AvailableLanguages
		{
			get { return Texts == null ? new Language[0] : Texts.Select(r => r.Language); }
		}

		/// <summary>
		/// Gets roles that allowed to view page.
		/// </summary>
		public virtual IList<Role> AllowRoles { get; protected set; }

		/// <summary>
		/// Initializes new instance.
		/// </summary>
		public Page()
		{
			ActionPath = new ActionPath();
			Texts = new List<PageText>();
			Tags = new List<Tag>();
			AllowRoles = new List<Role>();
		}
		
		/// <summary>
		/// Returns whether user has access to page.
		/// </summary>
		/// <param name="user">User to check. If <c>Null</c>, then user is anonymous.</param>
		/// <param name="checkOnlyDabase">True, to skip controller action attributes check. Default false.</param>
		/// <returns><c>True</c> if user has access. Otherwise <c>False</c>.</returns>
		public virtual bool HasAccess(User user, bool checkOnlyDabase = false)
		{
			// check action attributes
			if (!checkOnlyDabase && ActionPath != null)
			{
				ActionDescriptor[] actions = ActionPath.GetAction();
				if (actions != null)
				{
					foreach (ActionDescriptor action in actions)
					{
						CmsAuthorizeAttribute attr = action.GetCmsAuthorizeAttribute();
						if (attr != null)
							return attr.HasAccess(user);
					}
				}
			}

			// check database settings
			return (user == null && AllowAnonymous) ||
				(
					user != null && AllowAuthorized &&
					(
						AllowRoles.Count() == 0 ||
						AllowRoles.Any(r => user.Roles.Contains(r))
					)
				);
		}

		/// <summary>
		/// Returns page text by language.
		/// </summary>
		/// <param name="language">Language to search.</param>
		/// <returns>Page text or <c>Null</c> if not found.</returns>
		public virtual PageText GetText(Language language)
		{
			if (Texts == null)
				return null;
			return Texts.FirstOrDefault(pt => pt.Language.Id == language.Id);
		}

		/// <summary>
		/// Returns page text by language id.
		/// </summary>
		/// <param name="languageId">Language id to search.</param>
		/// <returns>Page text or <c>Null</c> if not found.</returns>
		public virtual PageText GetText(int languageId)
		{
			if (Texts == null)
				return null;
			return this.Texts.FirstOrDefault(pt => pt.Language.Id == languageId);
		}

		/// <summary>
		/// Returns page full path information by language.
		/// </summary>
		/// <param name="language">Language to search for.</param>
		/// <returns>Page full path info.</returns>
		public virtual PagePathItem[] GetPathInfo(Language language)
		{
			List<PagePathItem> path = new List<PagePathItem>();
			Page page = this;
			while (page != null)
			{
				PagePathItem item = new PagePathItem(page.Id, page.ActionPath, page.GetText(language));
				path.Insert(0, item);
				page = page.Parent;
			}

			return path.ToArray();
		}

		/// <summary>
		/// Returns page full page path.
		/// </summary>
		/// <returns>Page full path.</returns>
		public virtual string GetPath()
		{
			StringBuilder sb = new StringBuilder();
			Page page = this;
			while (page != null)
			{
				if (!String.IsNullOrEmpty(page.Name))
				{
					if (sb.Length != 0)
						sb.Insert(0, "/");
					sb.Insert(0, page.Name);
				}
				page = page.Parent;
			}

			return sb.ToString();
		}
	}
}
